// 各种不同的数组去重的方法 收集
// 参考： https://github.com/mqyqingfeng/Blog/issues/27
// https://segmentfault.com/a/1190000016418021
//


module.exports = arrayUnique

/**
 * 利用ES6 Set去重（ES6中最常用）
 *
 * @param  {[type]} arr [description]
 * @return {[type]}     [description]
 */
function arrayUnique (arr) {
	// [... new Set(arr)]
	return Array.from(new Set(arr))
}




/**
 * 利用for嵌套for，然后splice去重（ES5中最常用）
 * @param  {[type]} arr [description]
 * @return {[type]}     [description]
 */
function arrayUnique1 (array) {
  var arr = array.slice();

  for(var i=0; i<arr.length; i++) {
	    for(var j=i+1; j<arr.length; j++){

	    	// 识别 NaN的条件
	    	var nanConditon = typeof arr[i] === 'number' && isNaN(arr[i]) 
	    		&& typeof arr[j] === 'number' && isNaN(arr[j])
	    	//第一个等同于第二个，splice方法删除第二个
	    	// 全等比较 不能正确的比较NaN
	        if(arr[i] === arr[j] || nanConditon ){         
	            arr.splice(j--,1);
	        }
	    }
	}
	return arr;
}


/**
 * indexOf/includes 识别元素是否存在
 * @param  {[type]} arr [description]
 * @return {[type]}     [description]
 */
function arrayUnique2(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
    	// indexOf 不能失败出 NaN array.indexOf(arr[i]) === -1
    	// includes 可以 array.includes(arr[i]) === -1
        if (!array.includes(arr[i])) {
            array.push(arr[i])
        }
    }
    return array;
}

/**
 * 排序后只需要比较相邻的元素即可
 * 顺序发生了变化， NaN的元素不能识别
 * @param  {[type]} array [description]
 * @return {[type]}       [description]
 */
function arrayUnique_3 (array) {
	if (!Array.isArray(array)) {
       throw new TypeError('type error!')
    }
	var arr = array.slice();

	// 排序后， 只需要比较响铃的元素
	arr.sort()

	var result = [arr[0]]
	for (var i = 1; i < arr.length; i++) {
		if(arr[i] !== arr[i-1]) {
			result.push(arr[i])
		}
	}
	return result
}

/**
 * 利用对象的属性不能相同的特点进行去重
 * 对象的属性只能字符串， 对于数组元素不全部是字符串类型的，
 * 内部或做转换， 不建议使用
 * @param  {[type]} array [description]
 * @return {[type]}       [description]
 */
function arrayUnique_3_2 (array) {
	if (!Array.isArray(array)) {
       throw new TypeError('type error!')
    }
	
	var obj = {}
	var result = [arr[0]]
	for (var i = 1; i < arr.length; i++) {
		var one = arr[i]
		if(obj[one]) {
			obj[one] ++
		} else {
			obj[one] = 1
		}
	}

	return Object.keys(obj)
}

/**
 * 利用hasOwnProperty 判断是否存在对象属性, 所有的都去重了
 * @param  {[type]} array [description]
 * @return {[type]}       [description]
 */
function arrayUnique_3_3 (array) {
	if (!Array.isArray(array)) {
       throw new TypeError('type error!')
    }
	
	var obj = {}
	var ret = array.filter(item => {
		// 根据类型和值 一起组合当做key
		var key = typeof item + item;
		return obj.hasOwnProperty(key) ? false : (obj[key] = true)
	})

	return  ret 
}

/**
 * 利用filter 进行去重
 * 不能正确的处理 NaN的情况
 * @param  {[type]} array [description]
 * @return {[type]}       [description]
 */
function arrayUnique_3_4 (array) {
	if (!Array.isArray(array)) {
       throw new TypeError('type error!')
    }
	// 当前元素，在原始数组中的第一个索引==当前索引值，返回该元素
	// 相同的元素取第一个
	return array.filter((item, index) => {
		return array.indexOf(item, 0) === index 
	})
}



/**
 * 利用Map数据结构去重,
 * map与对象很类似都是键值对结构， map键可以是任意数据类型， 对象只能是字符串
 * map的键:
 * 	// 键的比较是基于 sameValueZero 算法： 
 *  // NaN 是与 NaN 相等的（虽然 NaN !== NaN），剩下所有其它的值是根据 === 运算符的结果判断是否相等。
 *
 * [arrayUnique4 description]
 * @param  {[type]} array [description]
 * @return {[type]}       [description]
 */
function arrayUnique4 (array) {
	var result = []
	var map = new Map()
	var one ;
	for (var i = 0; i < array.length; i++) {
		one = array[i]
		if(map.has(one)) {
			map.set(one, map.get(one)+1)
		} else {
			map.set(one, 1)
			result.push(one)
		}
	}
	return result
}


/**
 * 利用reduce+includes
 * @return {[type]} [description]
 */
function arrayUnique5(arr) {
	if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }

    return arr.reduce((prev, cur) => {
    	return prev.includes(cur) ? prev : [...prev, cur]
    }, [])
}


function test1() {

	var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
	// arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, 'NaN', 0, 0, 'a', 'a',{},{}];

	console.log(arrayUnique(arr))
	console.log(arrayUnique1(arr))
	console.log(arrayUnique2(arr))

	console.log(arrayUnique4(arr))
	console.log(arrayUnique5(arr))
	console.log(arrayUnique_3_4(arr))

	buildTest();
}



/**
 * 测试不同版本的函数，返回结果是否一致
 * @return {[type]} [description]
 */
function buildTest() {
	console.log(`\n------   buildTest 数组去重 ... ------\n`)
	
	// console.log(isExitsFunction('arrayUnique'))
	// console.log(getTestFunList(arrayUnique))
	
	// var fnList = getTestFunList(arrayUnique)
	// var endFn  = require('./03-ArrayLast.js')(fnList)
	// console.log(endFn , typeof endFn)
	
	myTest(arrayUnique, arr);

	/**
	 * 测试不同版本的 函数返回的结果是否一致
	 * @param  {Function}  fn   [description]
	 * @param  {...[type]} args [description]
	 * @return {[type]}         [description]
	 */
	function myTest(fn, ...args) {
		var fnList = getTestFunList(fn)

		var result = []
		for (var i = 0; i < fnList.length; i++) {
			result.push(fnList[i](...args))
		}

		var trasResult = result.map(item => JSON.stringify(item))
		var uniqueTransResult = [... new Set(trasResult)]
		
		// 几组函数的结果 返回值相同
		if(uniqueTransResult.length === 1) {
			return true
		} else {
			console.group('myTest result')
			console.log(result)
			console.groupEnd()
			
			return false 
		}
	}



	/**
	 * 获取多个同一功能，不能实现的函数列表
	 * @param  {Function} fn [description]
	 * @return {[type]}      [description]
	 */
	function getTestFunList(fn) {
		if(typeof fn !== 'function') {
			throw new TypeError('the argument must be an  function ')
		}

		var fnList = [fn]
		var fnMore = null
		var x = 0 
		while(x ++ < 10 ) {
			var isBreak = false ;
			var fnArr = []
			// 不同版本的函数，命名符合一定的规则
			var fnNames = [ fn.name + x , fn.name + `V${x}`, fn.name + `v${x}` ]
			
			for (var i = 0; i < fnNames.length; i++) {
				fnMore =  isExitsFunction(fnNames[i])
				if(fnMore) {
					fnArr.push(fnMore)
					// fnMore = null
				}
			}

			// 没有找到一个函数
			if(fnArr.length === 0 && x >= 2) {
				isBreak = true
			}
			
			fnList = fnList.concat(fnArr)
			if(isBreak) break
		}
		
		return  fnList
	}


	/**
	 * 判断一个函数(字符串)是否存在
	 * @param  {[type]}  funcName        [description]
	 * @param  {Boolean} transToFunction [description]
	 * @return {Boolean}                 [description]
	 */
	function isExitsFunction(funcName, transToFunction = true) {
	  try {
	  	var transFun = eval(funcName)
	    if (typeof(transFun) == "function") {
	      return transToFunction ? transFun : true
	    }
	  } catch(e) {}
	  return false 
	}
}
